use objc2::{declare_class, mutability, rc::Id, runtime::Sel, sel, ClassType, DeclaredClass};
use objc2_app_kit::{NSEvent, NSResponder, NSTextInputClient, NSView};
use objc2_foundation::{
	NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol,
	NSPoint, NSRange, NSRect, NSString, NSUInteger,
};

use crate::MTKView::MTKView;

use super::{Vars, WinEvent, WinRef};

declare_class!(
	pub	struct XLoopView;
	unsafe impl ClassType for XLoopView {
		#[inherits(NSView, NSResponder, NSObject)]
		type Super = MTKView;
		type Mutability = mutability::MainThreadOnly;
		const NAME: &'static str = "XLoopView";
	}
	impl DeclaredClass for XLoopView {
		type Ivars = Vars;
	}
	unsafe impl XLoopView {
		#[method(refresh)]
		fn refresh(&self) { self.refresh_() }
		#[method(acceptsFirstResponder)]
		fn accepts_first_responder(&self) -> bool { true }
		#[method(isFlipped)]
		fn is_flipped(&self) -> bool { true }

		#[method(draw)]
		unsafe fn draw(&self) { self.draw_() }

		#[method(mouseDown:)]
		unsafe fn mouse_down(&self, event: &NSEvent) { self.mouse(event) }
		#[method(mouseUp:)]
		unsafe fn mouse_up(&self, event: &NSEvent) { self.mouse(event) }
		#[method(mouseDragged:)]
		unsafe fn mouse_dragged(&self, event: &NSEvent) { self.mouse(event) }

		#[method(rightMouseDown:)]
		unsafe fn right_mouse_down(&self, event: &NSEvent) { self.mouse(event) }
		#[method(rightMouseUp:)]
		unsafe fn right_mouse_up(&self, event: &NSEvent) { self.mouse(event) }
		#[method(rightMouseDragged:)]
		unsafe fn right_mouse_dragged(&self, event: &NSEvent) { self.mouse(event) }

		#[method(otherMouseDown:)]
		unsafe fn other_mouse_down(&self, event: &NSEvent) { self.mouse(event) }
		#[method(otherMouseUp:)]
		unsafe fn other_mouse_up(&self, event: &NSEvent) { self.mouse(event) }
		#[method(otherMouseDragged:)]
		unsafe fn other_mouse_dragged(&self, event: &NSEvent) { self.mouse(event) }

		#[method(scrollWheel:)]
		unsafe fn scroll_wheel(&self, event: &NSEvent) { self.wheel(event) }

		#[method(keyDown:)]
		unsafe fn key_down(&self, event: &NSEvent) {
			self.interpretKeyEvents(&NSArray::from_slice(&[&*event]));
			self.keyboard(event)
		}
		#[method(keyUp:)]
		unsafe fn key_up(&self, event: &NSEvent) { self.keyboard(event) }
	}
	unsafe impl NSTextInputClient for XLoopView {
		#[method(hasMarkedText)]
		fn has_marked_text(&self) -> bool {
			log::trace!("hasMarkedText");
			self.ivars().marked_text.borrow().length() > 0
		}
		#[method(markedRange)]
		fn marked_range(&self) -> NSRange {
			log::trace!("markedRange");
			let length = self.ivars().marked_text.borrow().length();
			if length > 0 {
				NSRange::new(0, length)
			}else{
				NSRange::new(NSNotFound as NSUInteger, 0)
			}
		}
		#[method(selectedRange)]
		fn selected_range(&self) -> NSRange {
			log::trace!("selectedRange");
			NSRange::new(NSNotFound as NSUInteger, 0)
		}
		#[method(setMarkedText:selectedRange:replacementRange:)]
		fn set_marked_text(&self, string: &NSObject, selected_range: NSRange, _replacement_range: NSRange) {
			let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() {
				let string: *const NSObject = string;
				let string: *const NSAttributedString = string.cast();
				let string = unsafe { &*string };
				(
					NSMutableAttributedString::from_attributed_nsstring(string),
					string.string(),
				)
			} else {
				let string: *const NSObject = string;
				let string: *const NSString = string.cast();
				let string = unsafe { &*string };
				(
					NSMutableAttributedString::from_nsstring(string),
					string.copy(),
				)
			};
			log::info!("setMarkedText:selectedRange:replacementRange:{string:?} {selected_range:?}");
			let began = self.ivars().marked_text.borrow().length()<=0;
			*self.ivars().marked_text.borrow_mut() = marked_text;
			let action = began.then_some(types::Action::Update).unwrap_or(types::Action::Begin);
			self.composition(types::Ime::new(action,Some(string.to_string().into())));
		}
		#[method(unmarkText)]
		fn unmark_text(&self) {
			log::trace!("unmarkText");
			*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
			self.inputContext().expect("input context").discardMarkedText();
		}
		#[method_id(validAttributesForMarkedText)]
		fn valid_attributes_for_marked_text(&self) -> Id<NSArray<NSAttributedStringKey>> {
			log::trace!("validAttributesForMarkedText");
			NSArray::new()
		}
		#[method_id(attributedSubstringForProposedRange:actualRange:)]
		fn attributed_substring_for_proposed_range(&self, _range: NSRange, _actual_range: *mut NSRange) -> Option<Id<NSAttributedString>> {
			log::trace!("attributedSubstringForProposedRange:actualRange:");
			None
		}
		#[method(characterIndexForPoint:)]
		fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
			log::trace!("characterIndexForPoint:");
			0
		}
		#[method(firstRectForCharacterRange:actualRange:)]
		fn first_rect_for_character_range(&self, _range: NSRange, _actual_range: *mut NSRange) -> NSRect {
			log::trace!("firstRectForCharacterRange:actualRange:");
			let mut rect = NSRect::ZERO;
			rect.origin.y = 100.;
			// TODO
			self.window().expect("window").convertRectToScreen(self.convertRect_toView(rect, None))
		}
		#[method(insertText:replacementRange:)]
		fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
			log::info!("insertText:replacementRange:{string:?}");
			*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
			let string = if string.is_kind_of::<NSAttributedString>() {
				let string: *const NSObject = string;
				let string: *const NSAttributedString = string.cast();
				unsafe { &*string }.string().to_string()
			} else {
				let string: *const NSObject = string;
				let string: *const NSString = string.cast();
				unsafe { &*string }.to_string()
			};
			self.composition(types::Ime::new(types::Action::End,Some(string.into())));
		}
		#[method(doCommandBySelector:)]
		unsafe fn do_command_by_selector(&self, _command: Sel) {
			log::info!("doCommandBySelector:{_command:?}");
			if _command == sel!(insertNewline:) {
				self.composition(types::Ime::new(types::Action::End, Some("\n".into())));
				return;
			}
			if _command == sel!(insertTab:) {
				self.composition(types::Ime::new(types::Action::End, Some("\t".into())));
				return;
			}
			if _command == sel!(deleteBackward:) {
				self.composition(types::Ime::new(types::Action::Back, None));
				return;
			}
			use objc2::runtime::MessageReceiver;
			if self.respondsToSelector(_command) { self.send_message(_command, (self,)) }
		}
	}
);

impl XLoopView {
	fn refresh_(&self) {
		self.ivars().refresh.set(true);
	}
	fn draw_(&self) {
		if let Some(win) = self.window() {
			let time = crate::CABase::CACurrentMediaTime();
			let nanos = time * 1_000_000_000.;
			let nanos = nanos as i64;
			let vars = self.ivars();
			if vars.refresh.get() {
				vars.refresh.set(false);
				vars.rep.on_req_draw(WinRef(&win, &self), nanos);
			}
		}
	}
	fn mouse(&self, evt: &NSEvent) {
		if let Some(win) = self.window() {
			self.ivars().rep.on_mouse(WinRef(&win, self), WinEvent(evt));
		}
	}
	fn wheel(&self, evt: &NSEvent) {
		if let Some(win) = self.window() {
			self.ivars().rep.on_wheel(WinRef(&win, self), WinEvent(evt));
		}
	}
	fn keyboard(&self, evt: &NSEvent) {
		if let Some(win) = self.window() {
			self.ivars().rep.on_keyboard(WinRef(&win, self), WinEvent(evt));
		}
	}
	fn composition(&self, evt: types::Ime) {
		if let Some(win) = self.window() {
			self.ivars().rep.on_ime(WinRef(&win, self), &evt);
		}
	}
}
